package cz.pecinovsky.česky.oopnz.správce;
/*******************************************************************************
 *<pre>
 * Předchozí:   Není - toto_je_nově_definovaná_třída
 *              Ttt v projektu Ppp
 * Následující: Prozatím není
 *
 * Projekt  Ppp
 *   + Přidáno
 *   - Odebráno
 *   ~ Změněno
 *</pre>
 */


////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ Konec ignorovaného počátečního textu +++++

//%A+ >135
import cz.pecinovsky.česky.oopnz.utility.Hodiny;
import cz.pecinovsky.česky.oopnz.utility.IČasovaný;
//%A-
import cz.pecinovsky.česky.oopnz.utility.Pozice;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
//%I+ <135
//import java.util.Timer;
//import java.util.TimerTask;
//%I-


/*******************************************************************************
 * Instance třídy {@code Multipřesouvač} je jedináček sloužící
 * k přesunu několika grafických objektů zároveň.
 * Obdobně jako {@code SprávcePlátna} přijímá prostřednictvím
 * přesunových metod do správy objekty, kterými pak po plátně pohybuje.
 * <p>
 * Tyto objekty musí být typu {@link IPosuvný}. Je-li objekt dokonce typu
 * {@link IMultiposuvný}, tak poté, co objekt přesune do zadané cílové pozice,
 * zavolá jeho metodu {@link IMultiposuvný.přesunuto},
 * která může provést libovolnou akci (většinou znovu předá
 * objekt multipřesouvači, aby jej přesunul do další pozice).
 *
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Multipřesouvač
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

//%I+ <135
//    /** Implicitní hodnota periody = počet milisekund, které uplynou mezi
//     *  dvěma následnými překresleními přesouvaných objektů. */
//    private static final int PERIODA_0 = 50;
//%I-
    
    /** Tento atribut je tu pouze pro zjednodušení psaní. */
    private static final SprávcePlátna SP = SprávcePlátna.getInstance();

    /** Jediná existující instance multipřesouvače. */
    private static final Multipřesouvač jedináček = new Multipřesouvač();



//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================
//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    /** Časovač, který se postará o opakované vykreslování svěřených objektů.*/
//%I+ <135
//    private final Timer timer = new Timer();
//%I-
//%A+ >135
    private final Hodiny hodiny = Hodiny.getInstance();
//%A-

    /** Počet milisekund, které uplynou mezi dvěma následnými překresleními
     *  přesouvaných objektů. */
//%I+ <135
//    private final int perioda = PERIODA_0;
//%I-
//%A+ >135
    private final int perioda = hodiny.getPerioda();
//%A-
    
    /** Seznam posouvaných objektů. Je deklarovaný jako mapa,
     *  aby v něm bylo možno rychle testovat přítomnost prvku.
     *  Linkovanou mapou je proto, protože bude při kreslení velice často
     *  porcházen s vysokými požadavky na rychlost. */
    private final Map<IPosuvný,Animace> přesouvané =
                                        new LinkedHashMap<IPosuvný,Animace>();

    /** Tabulka dvojic [IPosuvný;Animace] reprezentovaná jako množina. */
    private final Set<Map.Entry<IPosuvný,Animace>> dvojice =
                                                        přesouvané.entrySet();



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** Proměnná ovládající běh multipřesouvače. */
    private Multipřesun multipřesun = null;



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================

    /***************************************************************************
     * Vrátí hodnotu periody =  počet milisekund, které uplynou mezi dvěma
     * následnými překresleními přesouvaných objektů.
     *
     * @return Perioda překreslování v milisekundách.
     */
    public int getPerioda()
    {
//%I+ <135
//        return perioda;
//%I-
//%A+ >135
        return hodiny.getPerioda();
//%A-
    }


    /***************************************************************************
     * Vrátí frekvenci překreslování, tj. počet překreslení za sekundu.
     * Frekvence překreslovnání může být i menší než 1.
     *
     * @return Frekvence překreslování.
     */
    public double getFrekvence()
    {
//%I+ <135
//        return 1000.0 / perioda;
//%I-
//%A+ >135
        return hodiny.getFrekvence();
//%A-
    }



//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vytvoří nový multipřesouvač s implicitní periodou (50 ms).
     * @return Požadovaný multipřesouvač
     */
    public static Multipřesouvač getInstance()
    {
        return jedináček;
    }


    /***************************************************************************
     * Vytvoří nový multipřesouvač - všechna potřebná nastavení
     * jsou součástí deklarací atributů.
     */
    private Multipřesouvač()
    {
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY ATRIBUTU INSTANCÍ =======================================

    /***************************************************************************
     * Vrátí pole se všemi právě přesouvanými objekty. Vrácený seznam je však
     * platný pouze v době volání. Vzápětí na to mohou být některé objekty
     * dovezeny do svých cílových pozic a mohou seznam "opustit".
     *
     * @return Pole právě přesouvaných objektů.
     */
    public IPosuvný[] getPřesouvané()
    {
        synchronized( přesouvané )
        {
            return přesouvané.keySet().toArray(new IPosuvný[0]);
        }
    }



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Za zadanou dobu
     * přesune zadaný posuvný objekt do zadané cílové pozice.
//%A+ >135
     * Požadovaná doba bude dodržena pouze tehdy,
     * nezmění-li uživatel v průběhu přesouvání frekvneci hodin.
//%A-
     *
     * @param sekund    Doba, kterou bude přesun trvat, v sekundách
     * @param ip        Přesouvaný objekt
     * @param pozice    Požadovaná cílové pozice
     */
    public void přesuňZaDobu(double sekund, IPosuvný ip, Pozice pozice)
    {
        přesuňZaDobu(sekund, ip, pozice.x, pozice.y);
    }


    /***************************************************************************
     * Za zadanou dobu
     * přesune zadaný posuvný objekt do zadané cílové pozice.
//%A+ >135
     * Požadovaná doba bude dodržena pouze tehdy,
     * nezmění-li uživatel v průběhu přesouvání frekvneci hodin.
//%A-
     *
     * @param sekund    Doba v sekundách, po kterou bude přesun trvat
     * @param ip        Přesouvaný objekt
     * @param xn        Vodorovná souřadnice cílové pozice
     * @param yn        Svislá souřadnice cílové pozice
     */
    public void přesuňZaDobu(double sekund, IPosuvný ip, int xn, int yn)
    {
        if( sekund <= 0 ) {
            throw new IllegalArgumentException(
                "Doba přesunu musí být kladná!");
        }
        přesuň( ip, xn, yn, 0, sekund*1000 );
    }


    /***************************************************************************
     * Zadanou rychlostí
     * přesune zadaný posuvný objekt do zadané cílové pozice.
//%A+ >135
     * Požadovaná rychlost bude dodržena pouze tehdy,
     * nezmění-li uživatel v průběhu přesouvání frekvneci hodin.
//%A-
     *
     * @param rychlost  Počet bodů, o které se objekt přesune za sekundu
     * @param ip        Přesouvaný objekt
     * @param pozice    Požadovaná cílové pozice
     */
    public void přesuňRychlostí(int rychlost, IPosuvný ip, Pozice pozice)
    {
        přesuňRychlostí(rychlost, ip, pozice.x, pozice.y);
    }


    /***************************************************************************
     * Zadanou rychlostí
     * přesune zadaný posuvný objekt do zadané cílové pozice.
//%A+ >135
     * Požadovaná rychlost bude dodržena pouze tehdy,
     * nezmění-li uživatel v průběhu přesouvání frekvneci hodin.
//%A-
     *
     * @param rychlost  Počet bodů, o které se objekt přesune za sekundu
     * @param ip        Přesouvaný objekt
     * @param xn        Vodorovná souřadnice cílové pozice
     * @param yn        Svislá souřadnice cílové pozice
     */
    public void přesuňRychlostí(int rychlost, IPosuvný ip, int xn, int yn)
    {
        if( rychlost <= 0 ) {
            throw new IllegalArgumentException(
                "Rychlost přesunu musí být kladná!");
        }
        přesuň( ip, xn, yn, rychlost, 0 );
    }


    /***************************************************************************
     * Zastaví požadovaný objekt, tj. vyjme jej ze seznamu objektů,
     * s nimiž pohybuje.
     * Pro příští rozpohybování tohoto objektu bude potřeba
     * znovu požádat multipřesouvač o jeho přesunutí.
     *
     * @param ip   Zastavovaný objekt
     *
     * @return     Informace o tom, byl-li objekt mezi přesouvanými
     */
    public boolean zastav( IPosuvný ip )
    {
        synchronized( přesouvané )
        {
            if( přesouvané.containsKey(ip) )
            {
                přesouvané.remove( ip );
                return true;
            }
            return false;
        }
    }


   /***************************************************************************
    * Zastaví veškeré přesuny a zruší všechny plány dalších přesunů.
    */
    public void zastavVše()
    {
        stop();
        synchronized( přesouvané )
        {
            přesouvané.clear();
        }
    }


//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Přesune zadaný posuvný objekt do požadované cílové pozice
     * za zadanou dobu nebo zadanou rychlostí.
     *
     * @param ip            Přesouvaný objekt
     * @param xn            Vodorovná souřadnice cílové pozice
     * @param yn            Svislá souřadnice cílové pozice
     * @param rychlost      Počet bodů, o které se objekt přesune za sekundu
     * @param milisekund    Doba, kterou bude přesun trvat, v sekundách
     */
    private void přesuň( IPosuvný ip, int xn, int yn,
                         int rychlost, double milisekund )
    {
        if( ip == null ) {
            throw new NullPointerException(
                "Přesouvaný objekt nesmí být null!");
        }
        Animace a = new Animace( ip, xn, yn, rychlost, milisekund  );
        synchronized(přesouvané)
        {
            if( přesouvané.get(ip) != null )
            {
                a = null;
                throw new IllegalStateException(
                    "Přidávaný objekt již je mezi přesouvanými!");
            }
//            SP.přidej( ip );
            přesouvané.put( ip, a );
            start();
        }//synchronized(přesouvané)
    }


    /***************************************************************************
     * Ukončí multipřesun a tím uvolní procesor.
     */
    private synchronized void stop()
    {
        if( multipřesun == null ) {
            return;
        }
//%I+ <135
//        multipřesun.cancel();
//%I-
//%A+ >135
        hodiny.odeber(multipřesun);
//%A-
        multipřesun = null;
    }


    /***************************************************************************
     * Spustí nový multipřesun.
     */
    private synchronized void start()
    {
        if( multipřesun == null )
        {
            multipřesun = new Multipřesun();
//%I+ <135
//            timer.scheduleAtFixedRate( multipřesun, perioda, perioda );
//            //timer.schedule(multipřesun, perioda, perioda);
//%I-
//%A+ >135
            hodiny.přidej(multipřesun);
            hodiny.start();
//%A-
        }
    }



//== INTERNÍ DATOVÉ TYPY =======================================================

    //==========================================================================
    /***************************************************************************
     * Soukromá vnitřní třída, jejíž instance sdružují všechny potřebné
     * informace o přesouvaném objektu. Funguje pouze jako přepravka.
     */
    private class Animace
    {
        double   x,  y;     //Aktuální souřadnice objektu
        double   dx, dy;    //Přírustek souřadnic v jednom kroku
        IPosuvný objekt;    //Animovaný objekt
        int      kroků;     //Počet kroků potřebných pro přesun.


        /***********************************************************************
         * Vytvoří animaci na základě zadaného objektu, jeho cílových
         * souřadnic a doby, během níž má těchto souřadnic dosáhnout.
         * nebo rychlosti, s níž se má k tomuto cíli přesouvat.
         * Volající metoda musí zabezpečit, aby doba nebo rychlost byla nulová.
         *
         * @param ip        Přesouvaný objekt
         * @param xn        Vodorovná souřadnice cíle
         * @param yn        Svislá souřadnice cíle
         * @param rychlost  Počet bodů "zdolaných" za sekundu
         * @param doba      Počet milisekund
         */
        Animace( IPosuvný ip, int xn, int yn, int rychlost, double doba )
        {
            objekt = ip;
//            x  = objekt.getX();
//            y  = objekt.getY();
            Pozice p = ip.getPozice();
            dx = (xn - p.x);
            dy = (yn - p.y);
            if( rychlost > 0 )
            {
            	double vzdálenost = Math.hypot( dx, dy );
                kroků = (int)(1000 * vzdálenost / (rychlost * perioda));
            }
            else
            {
            	kroků = (int)(doba / perioda);
            }
            if( kroků < 1 ) {
                kroků = 1;
            }
            //Posunutí zlepšuje rozložení přesunu při malém počtu kroků
            x  = p.x + 0.4;
            y  = p.y + 0.4;
            dx /= kroků;
            dy /= kroků;
        }


    }//private class Animace



    //==========================================================================
    /***************************************************************************
     * Instance třídy jsou úlohy ralizující vlastní multipřesun.
     * Multipřesun je definván jako samostatná třída proto, aby jej bylo možno
     * při ukončení všech zadaných přesunů vypnout a při vzniku nových
     * požadavků na přesun zase zapnout.
     */
//%I+ <135
//    private class Multipřesun extends TimerTask
//%I-
//%A+ >135
    private class Multipřesun implements IČasovaný
//%A-
    {
//%I+ <135
//        /***********************************************************************
//         * Metoda vyžadovaná rozhraním {@link Runable} implementovaným
//         * rodičovskou třídou {@link TimerTask} - tuto metodu zavolá
//         * {@link Timer} pokaždé, když se rozhodne spustit další provedení
//         * opakovaného úkolu ({@link Timertask}) - u nás multipřesunu.
//         */
//        @Override
//        public void run()
//%I-
//%A+ >135
        /***********************************************************************
         * Metoda vyžadovaná implementovaným rozhraním {@link IČasovaný}
         * Tuto metodu zavolá {@link SprávcePlátna} pokaždé,
         * když se rozhodne spustit další provedení opakovaného úkolu,
         * v našem případě = multipřesunu.
         */
        @Override
        public void tik()
//%A-
        {
            //Při překreslování se nesmí měnit počet objektů v seznamu
            synchronized(přesouvané)
            {
                //Potřebuji cyklus s iterátorem, protože se bude vyhazovat
                Iterator<Map.Entry<IPosuvný,Animace>> it = dvojice.iterator();
                SP.nekresli(); {
                    while( it.hasNext() )
                    {
                        Animace a = it.next().getValue();
                        a.x += a.dx;
                        a.y += a.dy;
                        a.objekt.setPozice((int)a.x, (int)a.y);
                        if( --a.kroků == 0 )
                        {
                            it.remove();
                            if( a.objekt instanceof IMultiposuvný )
                            {
                                final IMultiposuvný aa =
                                                    (IMultiposuvný) (a.objekt);
                                //Oznámení spuštím v novém vlákně,
                                //  abych na něj nemusel čekat
                                Thread t = new Thread(a.toString()) {
                                    @Override
                                    public void run() {
                                        aa.přesunuto();
                                    }//public void run()
                                };//Thread t = new Thread()
                                t.start();
                            }
                            //Při vyprázdnění seznamu zruš úlohu
                            if( přesouvané.size() <= 0)
                            {
                                stop();
                                break;
                            }
                        }
                    }//while
                } SP.vraťKresli();
            }//synchronized( přesouvané )
       }//public void run()

    }//private class Multipřesun extends TimerTask



//== TESTY A METODA MAIN =======================================================
}

